"""Selenium浏览器自动化支持

当常规爬虫方法失败时，使用Selenium作为备用方案
"""
import asyncio
import logging
import os
import time
from typing import Dict, Any, List, Optional, Callable
from pathlib import Path
from contextlib import asynccontextmanager

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import TimeoutException, WebDriverException
from selenium_stealth import stealth
import undetected_chromedriver as uc

logger = logging.getLogger(__name__)


class SeleniumDriver:
    """Selenium驱动管理器"""
    
    def __init__(self, headless: bool = True, use_undetected: bool = True):
        """初始化Selenium驱动
        
        Args:
            headless: 是否无头模式
            use_undetected: 是否使用反检测驱动
        """
        self.headless = headless
        self.use_undetected = use_undetected
        self.driver = None
        self.wait = None
        self.default_timeout = 10
        
    def _get_chrome_options(self) -> ChromeOptions:
        """获取Chrome选项
        
        Returns:
            Chrome选项对象
        """
        options = ChromeOptions()
        
        # 基础选项
        options.add_argument('--disable-blink-features=AutomationControlled')
        options.add_argument('--disable-dev-shm-usage')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-gpu')
        options.add_argument('--disable-web-security')
        options.add_argument('--disable-features=VizDisplayCompositor')
        options.add_argument('--disable-extensions')
        
        # 无头模式
        if self.headless:
            options.add_argument('--headless=new')  # 新版Chrome的无头模式
            
        # 设置窗口大小
        options.add_argument('--window-size=1920,1080')
        options.add_argument('--start-maximized')
        
        # 设置User-Agent
        options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
        
        # 禁用自动化提示
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        options.add_experimental_option('useAutomationExtension', False)
        
        # 设置偏好
        prefs = {
            'credentials_enable_service': False,
            'profile.password_manager_enabled': False,
            'profile.default_content_setting_values.notifications': 2,
            'profile.default_content_settings.popups': 0
        }
        options.add_experimental_option('prefs', prefs)
        
        return options
    
    def start(self) -> webdriver.Chrome:
        """启动浏览器
        
        Returns:
            Chrome驱动实例
        """
        try:
            if self.use_undetected:
                # 使用undetected-chromedriver
                self.driver = uc.Chrome(
                    options=self._get_chrome_options(),
                    version_main=None  # 自动检测版本
                )
            else:
                # 使用普通chromedriver
                options = self._get_chrome_options()
                self.driver = webdriver.Chrome(options=options)
                
                # 应用stealth插件
                stealth(self.driver,
                       languages=["zh-CN", "zh"],
                       vendor="Google Inc.",
                       platform="Win32",
                       webgl_vendor="Intel Inc.",
                       renderer="Intel Iris OpenGL Engine",
                       fix_hairline=True,
                       )
            
            # 设置隐式等待
            self.driver.implicitly_wait(10)
            
            # 创建显式等待对象
            self.wait = WebDriverWait(self.driver, self.default_timeout)
            
            logger.info("Selenium驱动启动成功")
            return self.driver
            
        except Exception as e:
            logger.error(f"启动Selenium驱动失败: {e}")
            raise
    
    def stop(self):
        """停止浏览器"""
        if self.driver:
            try:
                self.driver.quit()
                logger.info("Selenium驱动已关闭")
            except Exception as e:
                logger.error(f"关闭Selenium驱动失败: {e}")
            finally:
                self.driver = None
                self.wait = None
    
    def get_page(self, url: str, wait_element: str = None) -> str:
        """获取页面内容
        
        Args:
            url: 页面URL
            wait_element: 等待的元素选择器
            
        Returns:
            页面HTML
        """
        if not self.driver:
            self.start()
        
        try:
            self.driver.get(url)
            
            # 等待特定元素加载
            if wait_element:
                self.wait.until(
                    EC.presence_of_element_located((By.CSS_SELECTOR, wait_element))
                )
            else:
                # 默认等待body加载
                self.wait.until(
                    EC.presence_of_element_located((By.TAG_NAME, "body"))
                )
            
            # 额外等待，确保动态内容加载
            time.sleep(2)
            
            return self.driver.page_source
            
        except TimeoutException:
            logger.error(f"页面加载超时: {url}")
            return None
        except Exception as e:
            logger.error(f"获取页面失败: {e}")
            return None
    
    def scroll_to_bottom(self):
        """滚动到页面底部"""
        if self.driver:
            self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(1)
    
    def execute_script(self, script: str) -> Any:
        """执行JavaScript脚本
        
        Args:
            script: JavaScript代码
            
        Returns:
            执行结果
        """
        if self.driver:
            return self.driver.execute_script(script)
        return None
    
    def add_cookie(self, cookie: Dict[str, str]):
        """添加Cookie
        
        Args:
            cookie: Cookie字典
        """
        if self.driver:
            self.driver.add_cookie(cookie)
    
    def get_cookies(self) -> List[Dict]:
        """获取所有Cookie
        
        Returns:
            Cookie列表
        """
        if self.driver:
            return self.driver.get_cookies()
        return []
    
    def wait_and_click(self, selector: str, by: By = By.CSS_SELECTOR) -> bool:
        """等待元素并点击
        
        Args:
            selector: 元素选择器
            by: 选择器类型
            
        Returns:
            是否成功点击
        """
        try:
            element = self.wait.until(
                EC.element_to_be_clickable((by, selector))
            )
            element.click()
            return True
        except:
            return False
    
    def simulate_human_behavior(self):
        """模拟人类行为"""
        if not self.driver:
            return
        
        actions = ActionChains(self.driver)
        
        # 随机移动鼠标
        import random
        for _ in range(random.randint(2, 5)):
            x = random.randint(100, 800)
            y = random.randint(100, 600)
            actions.move_by_offset(x, y)
            actions.perform()
            time.sleep(random.uniform(0.1, 0.3))
        
        # 随机滚动
        scroll_times = random.randint(1, 3)
        for _ in range(scroll_times):
            scroll_height = random.randint(100, 500)
            self.driver.execute_script(f"window.scrollBy(0, {scroll_height});")
            time.sleep(random.uniform(0.5, 1.5))


class SeleniumSpider:
    """基于Selenium的爬虫基类"""
    
    def __init__(self, name: str = "SeleniumSpider"):
        """初始化Selenium爬虫
        
        Args:
            name: 爬虫名称
        """
        self.name = name
        self.driver_manager = None
        self.driver = None
        
    @asynccontextmanager
    async def get_driver(self, **kwargs):
        """获取驱动上下文管理器"""
        self.driver_manager = SeleniumDriver(**kwargs)
        try:
            self.driver = self.driver_manager.start()
            yield self.driver
        finally:
            if self.driver_manager:
                self.driver_manager.stop()
    
    async def fetch_with_selenium(self, url: str, wait_element: str = None,
                                 scroll: bool = False, cookies: List[Dict] = None) -> Optional[str]:
        """使用Selenium获取页面
        
        Args:
            url: 页面URL
            wait_element: 等待元素
            scroll: 是否滚动页面
            cookies: Cookie列表
            
        Returns:
            页面HTML
        """
        async with self.get_driver(headless=True, use_undetected=True) as driver:
            try:
                # 设置Cookie
                if cookies:
                    # 先访问域名设置cookie
                    from urllib.parse import urlparse
                    domain = urlparse(url).netloc
                    driver.get(f"https://{domain}")
                    
                    for cookie in cookies:
                        driver.add_cookie(cookie)
                
                # 访问目标页面
                driver.get(url)
                
                # 等待元素
                if wait_element:
                    wait = WebDriverWait(driver, 10)
                    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, wait_element)))
                
                # 模拟人类行为
                self.driver_manager.simulate_human_behavior()
                
                # 滚动页面
                if scroll:
                    self.driver_manager.scroll_to_bottom()
                
                # 获取页面源码
                html = driver.page_source
                
                logger.info(f"Selenium成功获取页面: {url}")
                return html
                
            except Exception as e:
                logger.error(f"Selenium获取页面失败: {e}")
                return None
    
    async def fetch_dynamic_content(self, url: str, js_script: str = None) -> Optional[Dict]:
        """获取动态加载的内容
        
        Args:
            url: 页面URL
            js_script: 要执行的JavaScript
            
        Returns:
            动态内容
        """
        async with self.get_driver(headless=True) as driver:
            try:
                driver.get(url)
                
                # 等待页面加载
                time.sleep(3)
                
                if js_script:
                    # 执行自定义脚本
                    result = driver.execute_script(js_script)
                else:
                    # 默认获取常见的动态数据
                    result = driver.execute_script("""
                        return {
                            title: document.title,
                            url: window.location.href,
                            content: document.body.innerText,
                            links: Array.from(document.querySelectorAll('a')).map(a => ({
                                text: a.innerText,
                                href: a.href
                            }))
                        };
                    """)
                
                return result
                
            except Exception as e:
                logger.error(f"获取动态内容失败: {e}")
                return None


class SeleniumPoolManager:
    """Selenium驱动池管理器"""
    
    def __init__(self, pool_size: int = 3):
        """初始化驱动池
        
        Args:
            pool_size: 池大小
        """
        self.pool_size = pool_size
        self.drivers = []
        self.available = asyncio.Queue()
        self.lock = asyncio.Lock()
        
    async def initialize(self):
        """初始化驱动池"""
        for i in range(self.pool_size):
            try:
                driver_manager = SeleniumDriver(headless=True, use_undetected=True)
                driver = driver_manager.start()
                self.drivers.append((driver_manager, driver))
                await self.available.put((driver_manager, driver))
                logger.info(f"初始化Selenium驱动 {i+1}/{self.pool_size}")
            except Exception as e:
                logger.error(f"初始化驱动失败: {e}")
    
    async def get_driver(self) -> tuple:
        """获取一个可用的驱动"""
        return await self.available.get()
    
    async def return_driver(self, driver_tuple: tuple):
        """归还驱动"""
        await self.available.put(driver_tuple)
    
    async def close_all(self):
        """关闭所有驱动"""
        for driver_manager, _ in self.drivers:
            driver_manager.stop()
        self.drivers.clear()


# 测试代码
async def test_selenium():
    """测试Selenium功能"""
    spider = SeleniumSpider("TestSpider")
    
    # 测试获取百度首页
    html = await spider.fetch_with_selenium("https://www.baidu.com", wait_element="#kw")
    if html and "百度" in html:
        print("✓ 成功获取百度首页")
    else:
        print("✗ 获取百度首页失败")
    
    # 测试获取动态内容
    content = await spider.fetch_dynamic_content("https://www.baidu.com")
    if content:
        print(f"✓ 获取动态内容: {content.get('title')}")
    else:
        print("✗ 获取动态内容失败")


if __name__ == "__main__":
    import asyncio
    asyncio.run(test_selenium())